package org.easy.eao.spring.hibernate;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.lang.StringUtils;
import org.easy.eao.annotations.Named;
import org.easy.eao.annotations.Query;
import org.easy.eao.annotations.Query.FirstResult;
import org.easy.eao.annotations.Query.MaxResults;
import org.easy.eao.utils.Parameter;
import org.easy.eao.utils.ReflectUtils;
import org.hibernate.HibernateException;
import org.hibernate.ScrollableResults;
import org.hibernate.Session;
import org.springframework.orm.hibernate3.HibernateCallback;

/**
 * 查询动作实现类
 * @author Frank
 */
public class QueryAction extends HibernateActionSupport<Query> {
	
	/**	返回类型 */
	private Class<?> returnType;
	/** 开始返回位置索引值 */
	private Integer firstResultIdx;
	/** 最大返回记录数索引值 */
	private Integer maxResultsIdx;
	/** 位置参数 */
	private List<Parameter> indexParams = new ArrayList<Parameter>(0);
	/** 命名参数 */
	private List<Parameter> namedParams = new ArrayList<Parameter>(0);
	
	@Override
	public void initialize(Annotation annotation, Method method, Class<?> clz) {
		super.initialize(annotation, method, clz);
		this.returnType = method.getReturnType();
		initizlizeParameters();
	}
	
	/**
	 * 初始化查询参数设置
	 * @param method
	 */
	private void initizlizeParameters() {
		Parameter[] parameters = ReflectUtils.getParameter(method);
		for (Parameter p : parameters) {
			if (p.hasAnnotation(FirstResult.class)) {
				firstResultIdx = p.getIndex();
				continue;
			}
			if (p.hasAnnotation(MaxResults.class)) {
				maxResultsIdx = p.getIndex();
				continue;
			}
			if (p.hasAnnotation(Named.class)) {
				namedParams.add(p);
				continue;
			}
			indexParams.add(p);
		}
	}
	
	@Override
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public Object execute(final Object[] parameters) {
		return getHibernateTemplate().execute(new HibernateCallback() {
			@Override
			public Object doInHibernate(Session session) throws HibernateException,
					SQLException {
				org.hibernate.Query query = getQuery(session, method);
				fittingQuery(query, parameters);
				return executeQuery(query, method);
			}
		});
	}

	/**
	 * 执行查询，更具不同的返回类型，以不同的查询方式执行
	 * @param query
	 * @param method
	 * @return
	 */
	protected Object executeQuery(org.hibernate.Query query, Method method) {
		if (List.class.isAssignableFrom(returnType))
			return query.list();
		if (Iterator.class.isAssignableFrom(returnType))
			return query.iterate();
		if (ScrollableResults.class.isAssignableFrom(returnType))
			return query.scroll();
		return query.uniqueResult();
	}

	/**
	 * 填充查询参数
	 * @param query
	 * @param parameters
	 */
	protected void fittingQuery(org.hibernate.Query query, Object[] parameters) {
		// Limit 设置
		if (firstResultIdx != null)
			query.setFirstResult((Integer) parameters[firstResultIdx]);
		if (maxResultsIdx != null)
			query.setMaxResults((Integer) parameters[maxResultsIdx]);
		// 按位置设置参数
		for (int i = 0; i < indexParams.size(); i++) {
			Parameter p = indexParams.get(i);
			query.setParameter(i, parameters[p.getIndex()]);
		}
		// 按名字设置参数
		for (Parameter p : namedParams) {
			Named named = p.getAnnotation(Named.class);
			setParameter(query, named.value(), parameters[p.getIndex()]);
		}
	}

	/**
	 * 设置命名查询参数
	 * @param query
	 * @param name
	 * @param para
	 */
	@SuppressWarnings("rawtypes")
	private void setParameter(org.hibernate.Query query, String name, Object para) {
		Class<?> clz = para.getClass();
		if (clz.isArray()) {
			query.setParameterList(name, (Object[]) para);
			return;
		}
		if (Collection.class.isAssignableFrom(para.getClass())) {
			query.setParameterList(name, (Collection) para);
			return;
		}
		query.setParameter(name, para);
	}

	/**
	 * 获取要执行的查询对象
	 * @param session
	 * @param method
	 * @return
	 */
	protected org.hibernate.Query getQuery(Session session, Method method) {
		org.hibernate.Query query = null;
		if (StringUtils.isNotBlank(annotation.named())) {
			query = session.getNamedQuery(annotation.named());			
		} else {
			query = session.createQuery(annotation.query());
			if (annotation.cacheable()) {
				query.setCacheable(true);
				if (StringUtils.isNotBlank(annotation.cacheRegion())) {
					query.setCacheRegion(annotation.cacheRegion());
				}
			}
		}
		return query;
	}

}
